/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import org.apache.tools.ant.taskdefs.condition.Os

plugins {
    id("de.undercouch.download")
    id("maven-publish")
    id("signing")
    alias(libs.plugins.android.library)
}

group = "com.facebook.react"
version = parent.publishing_version
def cmakeVersion = parent.cmake_version

/**
 * We use the bundled version of CMake in the Android SDK if available, to don't force Android
 * users to install CMake externally.
 */
def findCmakePath(cmakeVersion) {
    def cmakeRelativePath = "/cmake/${cmakeVersion}/bin/cmake"
    if (System.getenv("ANDROID_SDK_ROOT") && new File("${System.getenv("ANDROID_SDK_ROOT")}/${cmakeRelativePath}").exists()) {
        return "${System.getenv("ANDROID_SDK_ROOT")}/${cmakeRelativePath}"
    }
    if (System.getenv("ANDROID_HOME") && new File("${System.getenv("ANDROID_HOME")}/${cmakeRelativePath}").exists()) {
        return "${System.getenv("ANDROID_HOME")}/${cmakeRelativePath}"
    }
    return "cmake"
}

def reactNativeRootDir = project(':packages:react-native:ReactAndroid').projectDir.parent;
def customDownloadDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR")
def downloadsDir = customDownloadDir ? new File(customDownloadDir) : new File(reactNativeRootDir, "sdks/download")

// By default we are going to download and unzip hermes inside the /sdks/hermes folder
// but you can provide an override for where the hermes source code is located.
def overrideHermesDir = System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR") != null
def hermesDir = System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR") ?: new File(reactNativeRootDir, "sdks/hermes")
def hermesBuildDir = new File("$buildDir/hermes")
def hermesCOutputBinary = new File("$buildDir/hermes/bin/hermesc")

// This filetree represents the file of the Hermes build that we want as input/output
// of the buildHermesC task. Gradle will compute the hash of files in the file tree
// and won't rebuilt hermesc unless those files are changing.
def hermesBuildOutputFileTree = fileTree(hermesBuildDir.toString())
                    .include('**/*.make')
                    .include('**/*.cmake')
                    .include('**/*.marks')
                    .include('**/compiler_depends.ts')
                    .include('**/Makefile')
                    .include('**/link.txt')

def hermesVersion = "main"
def hermesVersionFile = new File(reactNativeRootDir, "sdks/.hermesversion")
if (hermesVersionFile.exists()) {
    hermesVersion = hermesVersionFile.text
}
def ndkBuildJobs = Runtime.runtime.availableProcessors().toString()
def prefabHeadersDir = new File("$buildDir/prefab-headers")

// By setting REACT_NATIVE_HERMES_SKIP_PREFAB you can skip prefab publishing, to
// reduce the size of the Hermes published .AAR.
def skipPrefabPublishing = System.getenv("REACT_NATIVE_HERMES_SKIP_PREFAB") != null

// We inject the JSI directory used inside the Hermes build with the -DJSI_DIR config.
def jsiDir = new File(reactNativeRootDir, "ReactCommon/jsi")

task downloadHermes(type: Download) {
    src("https://github.com/facebook/hermes/tarball/${hermesVersion}")
    onlyIfModified(true)
    overwrite(true)
    useETag("all")
    retries(5)
    dest(new File(downloadsDir, "hermes.tar.gz"))
}

task installArchives {
    dependsOn("publishAllPublicationsToNpmRepository")
}

task unzipHermes(dependsOn: downloadHermes, type: Copy) {
    from(tarTree(downloadHermes.dest)) {
        eachFile { file ->
            // We flatten the unzip as the tarball contains a `facebook-hermes-<SHA>`
            // folder at the top level.
            if (file.relativePath.segments.size() > 1) {
                file.relativePath = new org.gradle.api.file.RelativePath(!file.isDirectory(), file.relativePath.segments.drop(1))
            }
        }
    }
    into(hermesDir)
}

task configureBuildForHermes(type: Exec) {
    workingDir(hermesDir)
    inputs.dir(hermesDir)
    outputs.files(hermesBuildOutputFileTree)
    commandLine(
        windowsAwareCommandLine(
            findCmakePath(cmakeVersion),
            Os.isFamily(Os.FAMILY_WINDOWS) ? "-GNMake Makefiles" : "",
            "-S",
            ".",
            "-B",
            hermesBuildDir.toString(),
            "-DJSI_DIR=" + jsiDir.absolutePath
        )
    )
}

task buildHermesC(dependsOn: configureBuildForHermes, type: Exec) {
    workingDir(hermesDir)
    inputs.files(hermesBuildOutputFileTree)
    outputs.file(hermesCOutputBinary)
    commandLine(
        windowsAwareCommandLine(
            findCmakePath(cmakeVersion),
            "--build",
            hermesBuildDir.toString(),
            "--target",
            "hermesc",
            "-j",
            ndkBuildJobs,
        )
    )
}

task prepareHeadersForPrefab(type: Copy) {
    from("$hermesDir/API")
    from("$hermesDir/public")
    include("**/*.h")
    exclude("jsi/**")
    into(prefabHeadersDir)
}

static def windowsAwareCommandLine(String... commands) {
    def newCommands = []
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        newCommands = ['cmd', '/c']
    }
    newCommands.addAll(commands)
    return newCommands
}

def reactNativeArchitectures() {
    def value = project.getProperties().get("reactNativeArchitectures")
    return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}

repositories {
    // Normally RNGP will set repositories for all modules,
    // but when consumed from source, we need to re-declare
    // those repositories as there is no app module there.
    mavenCentral()
    google()
}

android {
    compileSdk libs.versions.compileSdk.get().toInteger()
    buildToolsVersion = libs.versions.buildTools.get()
    namespace "com.facebook.hermes"

    // Used to override the NDK path/version on internal CI or by allowing
    // users to customize the NDK path/version from their root project (e.g. for Apple Silicon support)
    if (rootProject.hasProperty("ndkPath")) {
        ndkPath rootProject.ext.ndkPath
    }
    if (rootProject.hasProperty("ndkVersion")) {
        ndkVersion rootProject.ext.ndkVersion
    }

    defaultConfig {
        minSdk = libs.versions.minSdk.get().toInteger()

        externalNativeBuild {
            cmake {
                arguments "-DHERMES_IS_ANDROID=True"
                arguments "-DANDROID_STL=c++_shared"
                arguments "-DANDROID_PIE=True"
                arguments "-DIMPORT_HERMESC=${new File(hermesBuildDir, "ImportHermesc.cmake").toString()}"
                arguments "-DJSI_DIR=${jsiDir}"
                arguments "-DHERMES_SLOW_DEBUG=False"
                arguments "-DHERMES_BUILD_SHARED_JSI=True"
                arguments "-DHERMES_RELEASE_VERSION=for RN ${version}"
                // We intentionally build Hermes with Intl support only. This is to simplify
                // the build setup and to avoid overcomplicating the build-type matrix.
                arguments "-DHERMES_ENABLE_INTL=True"
                // Due to https://github.com/android/ndk/issues/1693 we're losing Android
                // specific compilation flags. This can be removed once we moved to NDK 25/26
                arguments "-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON"
                targets "libhermes"
            }
        }
        ndk {
            abiFilters (*reactNativeArchitectures())
        }
    }

    externalNativeBuild {
        cmake {
            version cmakeVersion
            path "$hermesDir/CMakeLists.txt"
        }
    }

    buildTypes {
        debug {
            externalNativeBuild {
                cmake {
                    // JS developers aren't VM developers.
                    // Therefore we're passing as build type Release, to provide a faster build.
                    // This has the (unlucky) side effect of letting AGP call the build
                    // tasks `configureCMakeRelease` while is actually building the debug flavor.
                    arguments "-DCMAKE_BUILD_TYPE=Release"
                    // Adding -O3 to handle the issue here: https://github.com/android/ndk/issues/1740#issuecomment-1198438260
                    // The old NDK toolchain is not passing -O3 correctly for release CMake builds. This is fixed in NDK 25 and can be removed once we're there.
                    cppFlags "-O3"
                }
            }
        }
        release {
            externalNativeBuild {
                cmake {
                    arguments "-DCMAKE_BUILD_TYPE=MinSizeRel"
                    // For release builds, we don't want to enable the Hermes Debugger.
                    arguments "-DHERMES_ENABLE_DEBUGGER=False"
                }
            }
        }
    }

    sourceSets {
        main {
            manifest.srcFile "$hermesDir/android/hermes/src/main/AndroidManifest.xml"
            java.srcDirs = [
                "$hermesDir/lib/Platform/Intl/java"
            ]
        }
    }

    buildFeatures {
        prefab true
        prefabPublishing !skipPrefabPublishing
    }

    dependencies {
        implementation(libs.fbjni)
        implementation(libs.soloader)
        implementation(libs.yoga.proguard.annotations)
        implementation(libs.androidx.annotation)
    }

    packagingOptions {
        exclude "**/libc++_shared.so"
        exclude "**/libjsi.so"
        exclude "**/libfbjni.so"
    }

    publishing {
        multipleVariants {
            withSourcesJar()
            allVariants()
        }
    }

    prefab {
        libhermes {
            headers prefabHeadersDir.absolutePath
            libraryName "libhermes"
        }
    }
}

afterEvaluate {
    if (!overrideHermesDir) {
        // If you're not specifying a Hermes Path override, we want to
        // download/unzip Hermes from Github then.
        configureBuildForHermes.dependsOn(unzipHermes)
        prepareHeadersForPrefab.dependsOn(unzipHermes)
    }
    preBuild.dependsOn(buildHermesC)
    preBuild.dependsOn(prepareHeadersForPrefab)
    prepareHeadersForPrefab.dependsOn(buildHermesC)
}

/* Publishing Configuration */
apply from: "../publish.gradle"

// We need to override the artifact ID as this project is called `hermes-engine` but
// the maven coordinates are on `hermes-android`.
// Please note that the original coordinates, `hermes-engine`, have been voided
// as they caused https://github.com/facebook/react-native/issues/35210
publishing {
    publications {
        getByName("release") {
            artifactId 'hermes-android'
        }
    }
}
